package com.huwei.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;

import javax.imageio.ImageIO;

import lombok.Data;

/**
 * ImageIO.write() 很慢
 * @author huwei
 *
 */
public class CheckCodeUtil {
	static private char[] codeSequence = "1023546987zxcvbnmasdfghjklporkituyewq".toCharArray();
	
	final static private String KEY_PREFIX = "checkCode:";
	
	/**
	 * 缓存key
	 * @return
	 */
	static public String generateCheckCodeKey() {
		return KEY_PREFIX + UUID.randomUUID().toString();
	}

	/**
	 * 图片验证码 
	 * 随机从codeSequence获取codeCount个字符并填充到图片中，并会随机生成噪音点； 该方法生成的code顺序与图片顺序相同；
	 * @param width
	 * @param height
	 * @param codeCount
	 * @return
	 * @throws IOException
	 */
	static public CheckCodeVo imageCode(int width, int height, int codeCount) throws IOException {
		// 定义图像buffer
		BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics gd = templateGraphics(buffImg, width, height);
		nosieLine(height / 2, height / 2 + 2, gd, width, height);
		noiseRect(width, gd, width, height);

		// randomCode用于保存随机产生的验证码，以便用户登录后进行验证。
		StringBuffer randomCode = new StringBuffer();
		int xDistance = width / (codeCount + 1);
		// 随机产生codeCount数字的验证码。
		for (int i = 0; i < codeCount; i++) {
			// 得到随机产生的验证码数字。
			String code = randomCode();
			// 用随机产生的颜色将验证码绘制到图像中。
			gd.setColor(randomColor());
			gd.drawString(code, (i + 1) * xDistance, RandomUtil.nextInt(getFontHeight(height) - 2, height - 2));
			// 将产生的四个随机数组合在一起。
			randomCode.append(code);
		}
		return wapper(randomCode.toString(), buffImg);
	}

	/**
	 * 此方法没有经过测试，使用前请测试这种方法用于验证是否可行；
	 * 随机分布图片验证码
	 * 图片中字符的排列顺序与给定的字符是不一致的，可使用使用每个code的坐标点进行校验；
	 * @param width
	 * @param height
	 * @param codeCount
	 * @return
	 * @throws IOException 
	 */
	static public CheckCodeVo randomImageCode(int width, int height, int codeCount) throws IOException {
		BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics gd = templateGraphics(buffImg, width, height);
		nosieLine(height / 2, height / 2 + 2, gd, width, height);
		noiseRect(width, gd, width, height);

		List<String> codeList = new ArrayList<String>();
		Map<String,Object> pointMap = new HashMap<String, Object>();
		// 随机产生codeCount数字的验证码。
		for (int i = 0; i < codeCount; i++) {
			// 得到随机产生的验证码数字。
			String code = randomCode();
			while(pointMap.containsKey(code)) {
				code = randomCode();
			}
			int x = RandomUtil.nextInt(width - 10);
			int y = RandomUtil.nextInt(getFontHeight(height) - 2, height - 2);
			codeList.add(code);
			pointMap.put(code, x+","+y);
			
			// 用随机产生的颜色将验证码绘制到图像中。
			gd.setColor(randomColor());
			gd.drawString(code ,x ,y);
		}
		return wapper(codeList ,pointMap ,buffImg);
	}

	static private int getFontHeight(int imageHeight) {
		return (int) (imageHeight * 0.6);
	}

	static private Graphics templateGraphics(BufferedImage buffImg, int width, int height) {
		Graphics gd = buffImg.getGraphics();
		// 将图像填充为白色
		gd.setColor(Color.WHITE);
		gd.fillRect(0, 0, width, height);

		// 创建字体，字体的大小应该根据图片的高度来定。
		Font font = new Font("宋体", Font.CENTER_BASELINE, getFontHeight(height));
		// 设置字体。
		gd.setFont(font);

		// 画边框。
		gd.setColor(Color.GRAY);
		gd.drawRect(0, 0, width - 1, height - 1);

		return gd;
	}

	static private String randomCode() {
		return String.valueOf(codeSequence[RandomUtil.nextInt(codeSequence.length)]);
	}

	static private void nosieLine(int maxCount, int lineLenght, Graphics gd, int width, int height) {
		for (int i = 0, n = RandomUtil.nextInt(maxCount / 2, maxCount); i < n; i++) {
			gd.setColor(randomColor());
			int x = RandomUtil.nextInt(width);
			int y = RandomUtil.nextInt(height);
			gd.drawLine(x, y, x + lineLenght, y + lineLenght);
		}
	}

	static private void noiseRect(int maxCount, Graphics gd, int width, int height) {
		int sideLength = width / 100 < 1 ? 1 : width / 100;
		for (int i = 0, n = RandomUtil.nextInt(maxCount / 2, maxCount); i < n; i++) {
			gd.setColor(randomColor());
			// 随机噪音点
			gd.drawRect(RandomUtil.nextInt(width), RandomUtil.nextInt(height), sideLength, sideLength);
		}
	}

	static private Color randomColor() {
		return new Color(RandomUtil.nextInt(255), RandomUtil.nextInt(255), RandomUtil.nextInt(255));
	}

	static private CheckCodeVo wapper(List<String> codes, Map<String, Object> resultMap, BufferedImage image)
			throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ImageIO.write(image, "JPEG", baos);
		return new CheckCodeVo(codes, resultMap, baos.toByteArray());
	}

	static private CheckCodeVo wapper(String code, BufferedImage image) throws IOException {
		List<String> codes = new ArrayList<String>();
		codes.add(code);
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(code, code);
		return wapper(codes, map, image);
	}

	@Data
	static public class CheckCodeVo {
		List<String> codes;
		Map<String, Object> resultMap;
		byte[] image;

		public CheckCodeVo(List<String> codes, Map<String, Object> resultMap, byte[] image) {
			this.codes = codes;
			this.resultMap = resultMap;
			this.image = image;
		}

		public String getBase64() {
			return "data:image/jpeg;base64," + Base64.getEncoder().encodeToString(image);
		}
	}

	static private class RandomUtil {
		static final Random random = new Random();

		static int nextInt(int n) {
			return random.nextInt(n);
		}

		static int nextInt(int min, int max) {
			int n = nextInt(max);
			return n < min ? min : n;
		}
	}

//	static public void main(String[] args) throws IOException {
//		System.out.println(CheckCodeUtil.randomImageCode(120, 30, 4));
//	}
}
